import { useRenderStore } from '@/common/store';
import { useMemoizedFn } from 'ahooks';
import { userEvents } from 'nextcae-base';
import {
  ComponentType,
  createContext,
  MutableRefObject,
  PropsWithChildren,
  ReactNode,
  useContext,
  useEffect,
  useRef,
  useState,
} from 'react';
import { createPortal } from 'react-dom';
import { CSS2DObject } from '@amcax/web-render';

export enum CSS2DReactObjectEvents {
  Update = 'Update',
  Remove = 'Remove',
  UpdatePosition = 'UpdatePosition',
}

const CSS2DContext = createContext<{
  onAddWrapper: (id: string) => void;
  onRemoveWrapper: (id: string) => void;
  element: MutableRefObject<HTMLDivElement>;
}>(null);

export const useCSS2D = () => useContext(CSS2DContext);

const Wrapper = ({
  object,
  children,
}: PropsWithChildren<{ object: CSS2DReactObject }>) => {
  const [inputWrappers, setInputWrappers] = useState<string[]>([]);
  const [position, setPosition] = useState(object.screenPosition);

  const viewDom = useRenderStore.getState().mainRenderer.container;

  const onPositionChange = (obj: CSS2DReactObject) => {
    if (obj.uuid === object.uuid) {
      const rect = viewDom?.getBoundingClientRect();
      setPosition({
        x: obj.screenPosition.x + rect?.x,
        y: obj.screenPosition.y + rect?.y,
      });
    }
  };

  const onAddWrapper = useMemoizedFn((id) => {
    if (inputWrappers.includes[id]) {
      return;
    }

    setInputWrappers((old) => [...old, id]);
  });

  const onRemoveWrapper = useMemoizedFn((id) => {
    if (inputWrappers.includes[id]) {
      return;
    }

    setInputWrappers((old) => [...old, id]);
  });

  const divRef = useRef<HTMLDivElement>();

  useEffect(() => {
    if (inputWrappers.length >= 0) {
      userEvents.on(CSS2DReactObjectEvents.UpdatePosition, onPositionChange);
    }

    return () => {
      userEvents.off(CSS2DReactObjectEvents.UpdatePosition, onPositionChange);
    };
  }, [inputWrappers.length]);

  return (
    <CSS2DContext.Provider
      value={{
        onAddWrapper,
        onRemoveWrapper,
        element: divRef,
      }}>
      {children}
      {createPortal(
        <div
          key={object.uuid}
          ref={divRef}
          className='fixed top-0 left-0'
          style={{
            transform:
              'translate(-50%, -50%) translate(' +
              position?.x +
              'px,' +
              position?.y +
              'px)',
            display: inputWrappers.length === 0 && 'none',
          }}
        />,
        viewDom,
        object.uuid,
      )}
    </CSS2DContext.Provider>
  );
};

export class CSS2DReactObject<T = any> extends CSS2DObject {
  constructor(
    public readonly element: HTMLElement,
    ReactComponent: ComponentType<T>,
    public props: T,
    onHtmlRootRender?: VoidFunction,
    onRemove?: VoidFunction,
    onPositionChange?: (val: { x: number; y: number }) => void,
  ) {
    super(
      element,
      () => {
        onHtmlRootRender?.();
        userEvents.emit(CSS2DReactObjectEvents.Update, this);
      },
      () => {
        onRemove?.();
        userEvents.emit(CSS2DReactObjectEvents.Remove, this);
      },
      (val) => {
        onPositionChange?.(val);
        userEvents.emit(CSS2DReactObjectEvents.UpdatePosition, this);
      },
    );

    this.ReactComponent = (props) => {
      return (
        <Wrapper object={this}>
          <ReactComponent {...props} />
        </Wrapper>
      );
    };
  }

  ReactComponent: ComponentType<T>;

  updateProps(props: T) {
    this.props = props;
    if (this.parent != null) {
      userEvents.emit(CSS2DReactObjectEvents.Update, this);
    }
  }

  // 用于放置输入框的html，规避viewer-container下无法focus的问题
  private _inputNode: ReactNode;

  get inputNode() {
    return this._inputNode;
  }

  set inputNode(node) {
    this._inputNode = node;
    if (this.parent != null) {
      userEvents.emit(CSS2DReactObjectEvents.Update, this);
    }
  }
}
